package com.cyj.springbootservice.controller.nearby;

import com.cyj.springbootservice.api.entity.PostInfo;
import com.cyj.springbootservice.mapper.PostInfoMapper;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Rectangle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @description: 附近的人
 * <p>
 * MySQL+外接正方形
 * <p>
 * 外接矩形的实现方式是相对较为简单的一种方式。
 * <p>
 * 假设给定某用户的位置坐标, 求在该用户指定范围内的其他用户信息
 * <p>
 * 此时可以将位置信息和距离范围简化成平面几何题来求解
 * <p>
 * 以当前用户为圆心，以给定距离为半径画圆，那么在这个圆内的所有用户信息就是符合结果的信息，直接检索圆内的用户坐标难以实现，我们可以通过获取这个圆的外接正方形。
 * <p>
 * 通过外接正方形，获取经度和纬度的最大最小值，根据最大最小值可以将坐标在正方形内的用户信息搜索出来。
 * <p>
 * 此时在外接正方形中不属于圆形区域的部分就属于多余的部分，这部分用户信息距离当前用户（圆心）的距离必定是大于给定半径的，故可以将其剔除，最终获得指定范围内的附近的人
 * @Author C_Y_J
 * @create 2021/10/15 15:13
 **/
@RestController
@RequestMapping("/post")
public class PostInfo1Controller {

    @Autowired
    private PostInfoMapper postInfoMapper;

    private SpatialContext spatialContext = SpatialContext.GEO;

    /**
     * 获取附近x米的人
     * <p>
     * 自行计算外接正方形坐标及距离判断！！！！！！！！！！！！！！
     *
     * @param distance      距离范围 单位km
     * @param userLongitude 用户经度
     * @param userLatitude  用户纬度
     * @return json
     */
    @GetMapping("/nearby")
    public List<PostInfo> nearBySearch(@RequestParam("distance") double distance,
                                       @RequestParam("userLongitude") double userLongitude,
                                       @RequestParam("userLatitude") double userLatitude) {

        double[] point = getGpsRange(userLongitude, userLatitude, distance);

        //1.获取位置在正方形内的所有用户
        List<PostInfo> postInfos = postInfoMapper.selectPostInfo(point[0], point[1], point[2], point[3]);

        //2.剔除半径超过指定距离的多余用户
        List<PostInfo> collect = postInfos.stream().filter(
                a -> getDistance(
                        a.getLongitude(), a.getLatitude(), userLongitude, userLatitude
                ) <= distance
        ).collect(Collectors.toList());

        return collect;
    }


    /**
     * 获取附近x米的人
     * 使用第三方库计算外接正方形和距离！！！！！！！！
     *
     * @param distance      距离范围 单位km
     * @param userLongitude 当前经度
     * @param userLatitude  当前纬度
     * @return json
     */
    @GetMapping("/nearby1")
    public List<PostInfo> nearBySearch1(@RequestParam("distance") double distance,
                                        @RequestParam("userLongitude") double userLongitude,
                                        @RequestParam("userLatitude") double userLatitude) {

        Rectangle rectangle = getRectangle(distance, userLongitude, userLatitude);

        //1.获取位置在正方形内的所有用户
        List<PostInfo> postInfos = postInfoMapper.selectPostInfo(rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY());

        //2.剔除半径超过指定距离的多余用户
        List<PostInfo> collect = postInfos.stream().filter(
                a -> getDistance1(
                        a.getLongitude(), a.getLatitude(), userLongitude, userLatitude
                ) <= distance
        ).collect(Collectors.toList());

        return collect;
    }


    /*********************************************  手动实现的工具方法 ***************************************************************/


    //地球半径常量，km
    private static final double EARTH_RADIUS = 6378.137;

    /**
     * 查询出某个范围内的最大经纬度和最小经纬度
     *
     * @param userLongitude 用户当前位置经度
     * @param userLatitude  用户当前位置纬度
     * @param distance      距离范围，单位km
     * @return
     */
    public static double[] getGpsRange(double userLongitude, double userLatitude, double distance) {
        double dlng = 2 * Math.asin(Math.sin(distance / (2 * EARTH_RADIUS)) / Math.cos(userLatitude * Math.PI / 180));
        //角度转为弧度
        dlng = dlng * 180 / Math.PI;
        double dlat = distance / EARTH_RADIUS;
        dlat = dlat * 180 / Math.PI;
        double minlng = userLongitude - dlng;
        double maxlng = userLongitude + dlng;
        double minlat = userLatitude - dlat;
        double maxlat = userLatitude + dlat;
        return new double[]{minlng, maxlng, minlat, maxlat};
    }


    /**
     * 根据地球上任意两点的经纬度计算两点间的距离,返回距离单位：km
     * a.getLongitude(), a.getLatitude(), userLongitude, userLatitude
     *
     * @param longitude     坐标1 经度
     * @param latitude      坐标1 纬度
     * @param userLongitude 坐标2 用户经度
     * @param userLatitude  坐标2 用户纬度
     * @return 返回km
     */
    public static double getDistance(double longitude, double latitude, double userLongitude, double userLatitude) {
        double radLat1 = rad(latitude);
        double radLat2 = rad(userLatitude);
        double a = radLat1 - radLat2;
        double b = rad(longitude) - rad(userLongitude);
        double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        distance = distance * EARTH_RADIUS;
        distance = Math.round(distance * 10000) / 10000.0;
        return distance;
    }

    /**
     * 角度转弧度
     *
     * @param d
     * @return
     */
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }


    /*********************************************  第三方工具方法 ***************************************************************/

    /***
     * 球面中，两点间的距离（第三方库方法）
     *
     * @param longitude     坐标1 经度
     * @param latitude      坐标1 纬度
     * @param userLongitude 坐标2 用户经度
     * @param userLatitude  坐标2 用户纬度
     * @return 返回距离，单位km
     */
    private double getDistance1(Double longitude, Double latitude, double userLongitude, double userLatitude) {
        return spatialContext.calcDistance(
                spatialContext.makePoint(userLongitude, userLatitude),
                spatialContext.makePoint(longitude, latitude)
        ) * DistanceUtils.DEG_TO_KM;
    }


    /**
     * 利用开源库计算外接正方形坐标
     *
     * @param distance      距离范围，单位km
     * @param userLongitude 用户当前位置经度
     * @param userLatitude  用户当前位置纬度
     * @return
     */
    private Rectangle getRectangle(double distance, double userLongitude, double userLatitude) {
        return spatialContext.getDistCalc()
                .calcBoxByDistFromPt(
                        spatialContext.makePoint(userLongitude, userLatitude),
                        distance * DistanceUtils.KM_TO_DEG,
                        spatialContext, null);
    }

}
